private import std.string;
private import std.file;
private import std.path;
private import std.cstream;
private import std.ctype;
private import SDL;
private import SDL_mixer;

private:
static int g_width;
static int g_height;
static int g_videoBpp;
static int g_videoFlags;
static SDL_Surface* g_screen;
static SDL_Surface* g_buffer;
static SDL_Surface* [char[]] g_poolBMP;
static Mix_Chunk* [char[]] g_poolChunk;
static Mix_Music* g_music = null;
static SDL_Surface* [int] g_poolFont;
static bool g_quit = false;
static bool g_init = false;
static Uint8 g_pressMouseButtonPrev = 0;
static Uint8 g_pressMouseButton = 0;
static bool[SDLK_LAST] g_pressKeyButtonPrev;
static bool[SDLK_LAST] g_pressKeyButton;
static const char[] g_logname = "run.log";
/**
 * 十字キーのプラス方向の入力判定
 */
static const AXIS_PRESSED_PLUS  =  32768 * 8 / 10;
/**
 * 十字キーのマイナス方向の入力判定
 */
static const AXIS_PRESSED_MINUS = -32768 * 8 / 10;
bool isLeftJ(Sint16 axisX)
{
	return axisX < AXIS_PRESSED_MINUS;
}
bool isUpJ(Sint16 axisY)
{
	return axisY < AXIS_PRESSED_MINUS;
}
bool isRightJ(Sint16 axisX)
{
	return axisX > AXIS_PRESSED_PLUS;
}
bool isDownJ(Sint16 axisY)
{
	return axisY > AXIS_PRESSED_PLUS;
}
static Sint16 g_joyXPrev;
static Sint16 g_joyYPrev;
static Sint16 g_joyX;
static Sint16 g_joyY;
static bool[] g_joyButtonPrev;
static bool[] g_joyButton;
static SDL_Joystick* g_joy;
static int g_joyButtonNum;
static char[] FONT_PATH = "resource/font";

static int fps_cnt;
static int fps_view;
static Uint32 fps_lastTick;
/**
 * フルスクリーンモード切替
 */
void toggleFullScreen(SDL_Surface* screen, SDL_Surface* buffer)
{
	// バッファに保存
	SDL_BlitSurface(screen, cast(SDL_Rect*)0, buffer, cast(SDL_Rect*)0);
	g_videoFlags ^= SDL_FULLSCREEN; // フルスクリーンフラグ反転
	// スクリーン生成
	screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags);
	// 再描画
	SDL_BlitSurface(buffer, cast(SDL_Rect*)0, screen, cast(SDL_Rect*)0);
}
/**
 * マウスカーソル表示切替
 */
void toggleShowCursor()
{
	if(SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE) SDL_ShowCursor(SDL_DISABLE);
	else                                        SDL_ShowCursor(SDL_ENABLE);
}

/**
 * フォントの読込
 */
void loadFont()
{
	char[][] files = listdir(FONT_PATH);
	foreach (char[] fileName; files)
	{
		if(getExt(fileName) != "bmp") continue;
		SDL_Surface* font = SDL_LoadBMP(std.string.toStringz(FONT_PATH ~ "/" ~ fileName));
		if(font == null)
		{
			throw new Error("loadFont: " ~ std.string.toString(SDL_GetError()));
		}
		// フォント背景色は緑
		SDL_SetColorKey(font, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(font.format, 0, 255, 0));
		g_poolFont[cast(int)atoi(getBaseName(fileName))] = font;
	}
}

public:
const static uint HELL_BUTTON_LEFT   = SDL_PRESSED << (SDL_BUTTON_LEFT-1);
const static uint HELL_BUTTON_MIDDLE = SDL_PRESSED << (SDL_BUTTON_MIDDLE-1);
const static uint HELL_BUTTON_RIGHT  = SDL_PRESSED << (SDL_BUTTON_RIGHT-1);
enum {
	HELL_RETURN		= SDLK_RETURN,
	HELL_ESCAPE		= SDLK_ESCAPE,
	HELL_SPACE		= SDLK_SPACE,
	HELL_c			= SDLK_c,
	HELL_x			= SDLK_x,
	HELL_z			= SDLK_z,
	HELL_UP			= SDLK_UP,
	HELL_DOWN		= SDLK_DOWN,
	HELL_RIGHT		= SDLK_RIGHT,
	HELL_LEFT		= SDLK_LEFT,
}
enum {
	HELL_J_UP,
	HELL_J_DOWN,
	HELL_J_RIGHT,
	HELL_J_LEFT,
	HELL_J_MAX,
}

/**
 * SDL初期化
 */
void Hell_init(char[] caption, int width=640, int height=480, bool fullscreen=false)
{
	if(g_init) return;
	g_width      = width;
	g_height     = height;
	g_videoBpp   = 0;
//	g_videoFlags = SDL_SWSURFACE;
	g_videoFlags = SDL_DOUBLEBUF;
	if(fullscreen)
	{
		g_videoFlags |= SDL_FULLSCREEN;
	}
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0)
	{
		throw new Error("Hell_init: Couldn't initialize SDL");
	}
	// スクリーン初期化
	if((g_screen = SDL_SetVideoMode(g_width, g_height, g_videoBpp, g_videoFlags)) == null)
	{
		throw new Error("Hell_init: Couldn't set video mode");
	}
	// キャプション設定
	SDL_WM_SetCaption(std.string.toStringz(caption), null);
	// アイコン設定
	SDL_Surface* icon = SDL_LoadBMP(std.string.toStringz("resource/hell.bmp"));
	SDL_WM_SetIcon(icon, null);
	// バックバッファ生成
	g_buffer = SDL_ConvertSurface(g_screen, g_screen.format, g_videoFlags);
	if(!g_buffer)
	{
		throw new Error("Hell_init: Couldn't set video mode");
	}
	// サウンド初期化（音を良くしたい場合、下のコメントを外す）
//	if(Mix_OpenAudio(44100, AUDIO_S16, 2, 8192) < 0)
	if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024) < 0)
	{
		throw new Error("Hell_init: " ~ std.string.toString(SDL_GetError()));
	}
	// ログ初期化
	try{
		write(g_logname, "");
	}catch(FileException e){
		Hell_write(e);
		std.cstream.dout.writeLine("cant write log.\n");
	}

	if(SDL_NumJoysticks() > 0)
	{
		// ジョイスティック生成
		g_joy = SDL_JoystickOpen(0);
		if(!g_joy)
		{
			throw new Error(std.string.toString(SDL_GetError()));
		}
		g_joyButtonNum = SDL_JoystickNumButtons(g_joy);
		g_joyButtonPrev = new bool[g_joyButtonNum];
		g_joyButton = new bool[g_joyButtonNum];
	}
	else
	{
		g_joy = null;
		g_joyButtonNum = 0;
	}
	// フォントの読込
	loadFont();
	g_init = true;
}

/**
 * 終了処理
 */
void Hell_quit()
{
	if(g_joy) SDL_JoystickClose(g_joy);
	Mix_HaltChannel(-1);
	Mix_HaltMusic();
	if(!g_music)
	{
		Mix_FreeMusic(g_music);
	}
	foreach(chunk; g_poolChunk)
	{
		Mix_FreeChunk(chunk);
	}
	Mix_CloseAudio();
	SDL_Quit();
}

/**
 * ログ書き込み
 */
void Hell_write(char[] msg)
{
	std.cstream.dout.writeLine(msg);
	try{
		append(g_logname, msg ~ "\n");
	}catch(FileException e){
	}
}
void Hell_write(int msg)
{
	printf("%d\n", msg);
	try{
		append(g_logname, std.string.toString(msg) ~ "\n");
	}catch(FileException e){
	}
}
void Hell_write(Exception e)
{
	std.cstream.dout.writeLine(e.toString());
	try{
		append(g_logname, e.toString() ~ "\n");
	}catch(FileException e){
	}
}

/**
 * 画面を更新
 */
void Hell_update()
{
	SDL_Flip(g_screen);
//	SDL_UpdateRect(g_screen, 0, 0, 0, 0);
}

/**
 * いったん停止。入力を更新
 */
void Hell_wait(int ms)
{
	// ウエイト
	Uint32 nowTick , leftTick;
	static Uint32 prvTick = 0;
	
	if(prvTick == 0) prvTick = SDL_GetTicks();
	
	while (true){
	 	nowTick = SDL_GetTicks();
 		leftTick = prvTick + ms - nowTick;
 		if(leftTick < 1 || leftTick > 9999) break;
 	 	SDL_Delay(1);
	}
	prvTick = nowTick;
	
	// マウス更新
	g_pressMouseButtonPrev = g_pressMouseButton;
	g_pressMouseButton     = SDL_GetMouseState(null, null);

	// キーボード更新
	foreach(i, key; g_pressKeyButton)
	{
		g_pressKeyButtonPrev[i] = key;
	}
	Uint8* keys = SDL_GetKeyState(null);
	for(int i = 0; i < SDLK_LAST; i++)
	{
		g_pressKeyButton[i] = keys[i] == SDL_PRESSED;
	}
	// ジョイスティック更新
	SDL_JoystickUpdate();
	for(int i = 0; i < g_joyButtonNum; i++)
	{
		g_joyButtonPrev[i] = g_joyButton[i];
		g_joyButton[i] = SDL_JoystickGetButton(g_joy, i) == SDL_PRESSED;
	}
	g_joyXPrev = g_joyX;
	g_joyYPrev = g_joyY;
	g_joyX = SDL_JoystickGetAxis(g_joy, 0);
	g_joyY = SDL_JoystickGetAxis(g_joy, 1);

	// イベント更新
	SDL_Event e;
	while ( SDL_PollEvent(&e) ) g_quit = e.type == SDL_QUIT;
	if(Hell_isPushKey(SDLK_F5))      SDL_WM_IconifyWindow();
	if(Hell_isPushKey(SDLK_F9))      toggleFullScreen(g_screen, g_buffer);
	if(Hell_isPushKey(SDLK_F10))     toggleShowCursor();
	if(Hell_isPushKey(SDLK_F12))     SDL_SaveBMP(g_screen, "screenshot.bmp");
	if(Hell_isPressKey(SDLK_ESCAPE)) g_quit = true;
	if(g_quit) throw new Error("dHell system quit.");
	
	// fps計測
	fps_cnt++;
	if(cast(int)fps_lastTick / 1000 != cast(int)nowTick / 1000){
		fps_view = fps_cnt;
		fps_cnt = 0;
		fps_lastTick = nowTick;
	}
}

/**
 * 矩形の描画
 */
void Hell_fillRect(int x=0, int y=0, int w=0, int h=0, int r=0x00, int g=0x00, int b=0x00)
{
	SDL_Rect rect;
	rect.x = cast(short)x; rect.y = cast(short)y; rect.w = cast(ushort)w; rect.h = cast(ushort)h;
	if(w == 0 && h == 0)
	{
		rect.w = cast(ushort)g_width;
		rect.h = cast(ushort)g_height;
	}
	SDL_FillRect(g_screen, &rect, SDL_MapRGB(g_screen.format, cast(ubyte)r, cast(ubyte)g, cast(ubyte)b));
}

/**
 * BMP読込み
 * @param key   キー
 * @param path  画像ファイルのパス
 * @param r,g,b 抜き色（-2で抜き色なし。-1で左上の色）
 */
void Hell_loadBMP(char[] key, char[] path, int r=-2, int g=0, int b=0)
{
	if(key in g_poolBMP)
	{
		throw new Error("Hell_loadBMP: Has exist key: " ~ key);
	}
	SDL_Surface* image = SDL_LoadBMP(std.string.toStringz(path));
	if(image == null)
	{
		throw new Error("Hell_loadBMP: " ~ std.string.toString(SDL_GetError()));
	}
	if(r == -1)
	{
		SDL_PixelFormat* fmt = image.format;
		SDL_LockSurface(image); // ロック
		// 左上ピクセル取得
		Uint32 index = *(cast(Uint32*)image.pixels);
		Uint8 rr, gg, bb;
		SDL_GetRGB(index, fmt, &rr, &gg, &bb);
		SDL_UnlockSurface(image); // アンロック
		SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image.format, rr, gg, bb));
	}
	else if(r >= 0)
	{
		SDL_SetColorKey(image, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(image.format, cast(ubyte)r, cast(ubyte)g, cast(ubyte)b));
	}
	g_poolBMP[key] = image;
}

/**
 * BMPをスクリーンに描画
 * @param key     画像のキー
 * @param px, py  描画開始座標（左上）
 */
void Hell_draw(char[] key, int px, int py, int sx=0, int sy=0, int sw=0, int sh=0)
{
	if(!(key in g_poolBMP))
	{
		throw new Error("Hell_draw: Has exist key: " ~ key);
	}
	SDL_Surface* surface = g_poolBMP[key];
	SDL_Rect dst;
	dst.x = cast(short)px;
	dst.y = cast(short)py;

	SDL_Rect src;
	src.x = cast(short)sx;
	src.y = cast(short)sy;
	src.w = cast(ushort)surface.w;
	src.h = cast(ushort)surface.h;
	if(sw != 0 && sh != 0)
	{
		src.w = cast(ushort)sw;
		src.h = cast(ushort)sh;
	}
	SDL_BlitSurface(surface, &src, g_screen, &dst);
	
}

/**
 * 画像にアルファ値を設定します
 * @param key   画像のキー
 * @param alpha アルファ値
 */
void Hell_setAlpha(char[] key, int alpha)
{
	if(!(key in g_poolBMP))
	{
		throw new Error("Hell_setAlpha: Has exist key: " ~ key);
	}
	SDL_SetAlpha(g_poolBMP[key], SDL_SRCALPHA, cast(ubyte)alpha);
}

/**
 * マウス座標Xの取得
 */
int Hell_getMouseX()
{
	int x, y;
	Uint8 button = SDL_GetMouseState(&x, &y);
	return x;
}

/**
 * マウス座標Yの取得
 */
int Hell_getMouseY()
{
	int x, y;
	Uint8 button = SDL_GetMouseState(&x, &y);
	return y;
}

/**
 * マウスを押し続けているかどうか
 */
Uint8 Hell_isPressMouse()
{
	return g_pressMouseButton;
}

/**
 * マウスをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
Uint8 Hell_isPushMouse()
{
	return cast(ubyte)(g_pressMouseButton ^ (g_pressMouseButton & g_pressMouseButtonPrev));
}

/**
 * キーを押し続けているかどうか
 */
bool Hell_isPressKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id];
}

/**
 * キーをその瞬間に押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushKey(int id)
{
	if(id < 0 || SDLK_LAST <= id) return false;
	return g_pressKeyButton[id] && !g_pressKeyButtonPrev[id];
}


/**
 * SE（WAVファイル）の読み込み
 * @param key  キー
 * @param path サウンドファイルのパス
 */
void Hell_loadWAV(char[] key, char[] path)
{
	if(key in g_poolBMP)
	{
		throw new Error("Hell_loadWAV: Has exist key: " ~ key);
	}
	Mix_Chunk* chunk = Mix_LoadWAV(std.string.toStringz(path));
	if(!chunk)
	{
		throw new Error("Hell_loadWAV: " ~ std.string.toString(SDL_GetError()) ~ 
                         " (" ~ path ~ ")");
	}
	g_poolChunk[key] = chunk;
}

/**
 * SE（WAVファイル）の再生
 * @param key   キー
 * @param loops ループ回数（-1で無限ループ）
 */
void Hell_playWAV(char[] key, int loops=0)
{
	if(!(key in g_poolChunk))
	{
		throw new Error("Hell_playWAV: Has exist key: " ~ key);
	}
	Mix_PlayChannel(-1, g_poolChunk[key], loops);
}

/**
 * SE（WAVファイル）を全て停止
 */
void Hell_stopWAVAll()
{
	Mix_HaltChannel(-1);
}

/**
 * ジョイスティックの十字キーを押しているかどうか
 */
bool Hell_isPressJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return isUpJ(g_joyY);
	case HELL_J_DOWN:
		return isDownJ(g_joyY);
	case HELL_J_LEFT:
		return isLeftJ(g_joyX);
	case HELL_J_RIGHT:
		return isRightJ(g_joyX);
	default:
		return false;
	}
	return false;
}

/**
 * ジョイスティックの十字キーを押したかどうか
 */
bool Hell_isPushJKey(int id)
{
	if(!g_joy) return false;
	if(id < 0 || HELL_J_MAX < id) return false;
	switch(id)
	{
	case HELL_J_UP:
		return isUpJ(g_joyY) && !isUpJ(g_joyYPrev);
	case HELL_J_DOWN:
		return isDownJ(g_joyY) && !isDownJ(g_joyYPrev);
	case HELL_J_LEFT:
		return isLeftJ(g_joyX) && !isLeftJ(g_joyXPrev);
	case HELL_J_RIGHT:
		return isRightJ(g_joyX) && !isRightJ(g_joyXPrev);
	default:
		return false;
	}
	return false;
}

/**
 * ジョイスティックのボタンを押しているかどうか
 */
bool Hell_isPressJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id];
}

/**
 * ジョイスティックのボタンを押したかどうか
 * <br>
 * Hell_wait()で更新
 */
bool Hell_isPushJButton(int id)
{
	if(!g_joy) return false;
	if(id < 0 || g_joyButtonNum < id) return false;
	return g_joyButton[id] && !g_joyButtonPrev[id];
}

/**
 * BGMを再生する
 * @param path  ファイルパス
 * @param loops ループ回数（-1で無限ループ）
 * @param ms    フェードイン時間（ms）
 */
void Hell_playBgm(char[] path, int loops=-1, int ms=0)
{
	if(g_music)
	{
		Mix_HaltMusic();
		Mix_FreeMusic(g_music);
	}
	g_music = Mix_LoadMUS(std.string.toStringz(path));
	if(!g_music)
	{
		throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
	}
	if(ms <= 0)
	{
		if(Mix_PlayMusic(g_music, loops) != 0)
		{
			throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
		}
	}
	else
	{
		if(Mix_FadeInMusic(g_music, loops, ms) != 0)
		{
			throw new Error("Hell_playBgm: " ~ std.string.toString(SDL_GetError()));
		}
	}
}

/**
 * BGMを停止する
 * @param ms    フェードアウト時間（ms）
 */
void Hell_stopBgm(int ms)
{
	if(g_music)
	{
		if(ms <= 0)
		{
			Mix_HaltMusic();
			Mix_FreeMusic(g_music);
			g_music = null;
		}
		else
		{
			Mix_FadeOutMusic(ms);
		}
	}
}

/**
 * フォントの描画（asciiのみです）
 * @param msg   ascii文字列
 * @param x,y   描画開始位置
 * @param alpha アルファ値
 */
void Hell_drawFont(char[] msg, int x, int y, int alpha=255)
{
	foreach(c; msg)
	{
		if(!isascii(c)) continue;
		int idx = cast(int)c;
		if(idx == 32)
		{
			x += 9; // スペース
			continue;
		}
		if(idx in g_poolFont)
		{
			SDL_Rect dst;
			dst.x = cast(short)x;
			dst.y = cast(short)y;
			SDL_SetAlpha(g_poolFont[idx], SDL_SRCALPHA, cast(ubyte)alpha);
			SDL_BlitSurface(g_poolFont[idx], cast(SDL_Rect*)0, g_screen, &dst);
			x += g_poolFont[idx].w;
		}
	}

}
void Hell_drawFont(int msg, int x, int y, int alpha=255)
{
	Hell_drawFont(std.string.toString(msg), x, y, alpha);
}

int Hell_getFPS(){
	return fps_view;
}
